﻿/**
* @author Trevor McCauley
* @link www.senocular.com
*/

package org.papervision3d.core.utils.virtualmouse;


import nme.display.DisplayObject;
import nme.display.DisplayObjectContainer;
import nme.display.InteractiveObject;
import nme.display.SimpleButton;
import nme.display.Sprite;
import nme.display.Stage;
import nme.events.Event;
import nme.events.EventDispatcher;
import nme.events.KeyboardEvent;
import nme.events.MouseEvent;
import nme.geom.Point;
//import nme.utils.Dictionary;
import nme.ObjectHash;

import org.papervision3d.core.log.PaperLogger;

/**
 * Dispatched when the virtual mouse state is updated.
 * @eventType flash.events.Event
 */
//[Event(name="update", type="flash.events.Event")]

/**
 * Dispatched when the virtual mouse fires an
 * Event.MOUSE_LEAVE event.
 * @eventType flash.events.Event
 */
//[Event(name="mouseLeave", type="flash.events.Event")]

/**
 * Dispatched when the virtual mouse fires an
 * MouseEvent.MOUSE_MOVE event.
 * @eventType flash.events.MouseEvent
 */
//[Event(name="mouseMove", type="flash.events.MouseEvent")]

/**
 * Dispatched when the virtual mouse fires an
 * MouseEvent.MOUSE_OUT event.
 * @eventType flash.events.MouseEvent
 */
//[Event(name="mouseOut", type="flash.events.MouseEvent")]
/**
 * Dispatched when the virtual mouse fires an
 * MouseEvent.ROLL_OUT event.
 * @eventType flash.events.MouseEvent
 */
//[Event(name="rollOut", type="flash.events.MouseEvent")]

/**
 * Dispatched when the virtual mouse fires an
 * MouseEvent.MOUSE_OVER event.
 * @eventType flash.events.MouseEvent
 */
//[Event(name="mouseOver", type="flash.events.MouseEvent")]

/**
 * Dispatched when the virtual mouse fires an
 * MouseEvent.ROLL_OVER event.
 * @eventType flash.events.MouseEvent
 */
//[Event(name="rollOver", type="flash.events.MouseEvent")]

/**
 * Dispatched when the virtual mouse fires an
 * MouseEvent.MOUSE_DOWN event.
 * @eventType flash.events.MouseEvent
 */
//[Event(name="mouseDown", type="flash.events.MouseEvent")]

/**
 * Dispatched when the virtual mouse fires an
 * MouseEvent.MOUSE_UP event.
 * @eventType flash.events.MouseEvent
 */
//[Event(name="mouseUp", type="flash.events.MouseEvent")]

/**
 * Dispatched when the virtual mouse fires an
 * MouseEvent.CLICK event.
 * @eventType flash.events.MouseEvent
 */
//[Event(name="click", type="flash.events.MouseEvent")]

/**
 * Dispatched when the virtual mouse fires an
 * MouseEvent.DOUBLE_CLICK event.
 * @eventType flash.events.MouseEvent
 */
//[Event(name="doubleClick", type="flash.events.MouseEvent")]

/**
 * The VirtualMouse class is used to create a programmatic 
 * version of the users mouse that can be moved about the
 * Flash player stage firing off mouse events of the display
 * objects it interacts with.  This can allow you to simulate
 * interaction with buttons and movie clips through ActionScript.
 * <br />
 * Handled events include:
 * 		Event.MOUSE_LEAVE,
 * 		MouseEvent.MOUSE_MOVE,
 * 		MouseEvent.MOUSE_OUT,
 * 		MouseEvent.ROLL_OUT,
 * 		MouseEvent.MOUSE_OVER,
 * 		MouseEvent.ROLL_OVER,
 * 		MouseEvent.MOUSE_DOWN,
 * 		MouseEvent.MOUSE_UP.
 * 		MouseEvent.CLICK, and,
 * 		MouseEvent.DOUBLE_CLICK.
 * Along with dispatching those events for their respective
 * targets, the VirtualMouse instance will also dispatch the
 * event on itself allowing to capture which events are being
 * fired by the virtual mouse.  The last event fired can also
 * be referenced in the lastEvent property.
 * <br />
 * VirtualMouse mouse cannot:
 * 		activate states of SimpleButton instances, 
 * 		change object focus, 
 * 		handle mouseWheel related events,
 * 		change the system's cursor location, or 
 * 		spoof the location of the mouseX and mouseY properties
 * 			(which some components rely on).
 */
class VirtualMouse extends EventDispatcher {
	
	public static inline var UPDATE:String = "update";
	
	private var altKey:Bool = false;
	private var ctrlKey:Bool = false;
	private var shiftKey:Bool = false;
	private var delta:Int = 0; // mouseWheel unsupported
	
	private var _stage:Stage;
	private var _container:Sprite;
	private var target:InteractiveObject;
	
	private var location:Point;
	
	private var isLocked:Bool = false;
	private var isDoubleClickEvent:Bool = false;
	private static var _mouseIsDown:Bool = false;
	
	private var disabledEvents:Hash<Bool>;
	private var ignoredInstances:ObjectHash<DisplayObject, Bool>;
	
	private var _lastEvent:Event;
	private var lastMouseDown:Bool;
	private var updateMouseDown:Bool;
	private var lastLocation:Point;
	private var lastDownTarget:DisplayObject;
	private var lastWithinStage:Bool;
		
	private var _useNativeEvents:Bool;
	private var eventEvent:Class<Event>;
	private var mouseEventEvent:Class<Event>;
	
	/** 
	 * Initializes a new VirtualMouse instance. 
	 * @param stage A reference to the stage instance.
	 * @param startX The initial x location of
	 * 		the virtual mouse.
	 * @param startY The initial y location of
	 * 		the virtual mouse.
	 */
	public function new(stage:Stage = null, container:Sprite = null, startX:Float = 0, startY:Float = 0) {
		super();
		
		altKey = false;
		ctrlKey = false;
		shiftKey = false;
		delta = 0; // mouseWheel unsupported

		isLocked = false;
		isDoubleClickEvent = false;
		
		disabledEvents = new Hash<Bool>();
		ignoredInstances = new ObjectHash<DisplayObject, Bool>();

		lastMouseDown = false;
		updateMouseDown = false;
		lastWithinStage = true;
			
		_useNativeEvents = false;
		eventEvent = VirtualMouseEvent;
		mouseEventEvent = VirtualMouseMouseEvent;
		
		
		this.stage = stage;
		this.container = container;
		location = new Point(startX, startY);
		lastLocation = location.clone();
		addEventListener(UPDATE, handleUpdate);
		update();
	}
	
	/**
	 * A reference to the Stage instance. This
	 * reference needs to be passed to the 
	 * VirtualMouse instance either in its inline varructor
	 * or through assigning it's stage property.
	 * Without a valid reference to the stage, the
	 * virtual mouse will not function.
	 * @see VirtualMouse()
	 */
	public var stage(get_stage, set_stage):Stage;
	private function get_stage():Stage {
		return _stage;
	}
	private function set_stage(s:Stage):Stage {
		var hadStage:Bool;
		if (_stage != null){
			hadStage = true;
			_stage.removeEventListener(KeyboardEvent.KEY_DOWN, keyHandler);
			_stage.removeEventListener(KeyboardEvent.KEY_UP, keyHandler);
		}else{
			hadStage = false;
		}
		_stage = s;
		if (_stage != null) {
			_stage.addEventListener(KeyboardEvent.KEY_DOWN, keyHandler);
			_stage.addEventListener(KeyboardEvent.KEY_UP, keyHandler);
			target = _stage;
			if (!hadStage) update();
		}
		return s;	
	}
	
	/**
	* 
	* @param	value Sprite container you want VirtualMouse to use with its testing of sub containers
	* @return
	*/
	public var container(get_container, set_container):Sprite;
	private function set_container(value:Sprite):Sprite
	{
		_container = value;
		return value;
	}
	private function get_container():Sprite { return _container; }
	
	/**
	 * The last event dispatched by the VirtualMouse
	 * instance.  This can be useful for preventing
	 * event recursion if performing VirtualMouse
	 * operations within MouseEvent handlers.
	 */
	public var lastEvent(get_lastEvent, null):Event;
	private function get_lastEvent():Event {
		return _lastEvent;
	}
	
	/**
	 * True if the virtual mouse is being
	 * pressed, false if not.  The mouse is
	 * down for the virtual mouse if press()
	 * was called.
	 * @see press()
	 * @see release()
	 */
	public var mouseIsDown(get_mouseIsDown, null):Bool;
	private function get_mouseIsDown():Bool {
		return _mouseIsDown;
	}
	
	/**
	 * The x location of the virtual mouse. If you are
	 * setting both the x and y properties of the
	 * virtual mouse at the same time, you would probably
	 * want to lock the VirtualMouse instance to prevent
	 * additional events from firing.
	 * @see lock
	 * @see unlock
	 * @see y
	 * @see setLocation()
	 * @see getLocation()
	 */
	public var x(get_x, set_x):Float;
	private function get_x():Float {
		return location.x;
	}
	private function set_x(n:Float):Float {
		location.x = n;
		if (!isLocked) update();
		
		return n;
	}
	
	/**
	 * The y location of the virtual mouse.  If you are
	 * setting both the x and y properties of the
	 * virtual mouse at the same time, you would probably
	 * want to lock the VirtualMouse instance to prevent
	 * additional events from firing.
	 * @see lock
	 * @see unlock
	 * @see x
	 * @see setLocation()
	 * @see getLocation()
	 */
	public var y(get_y, set_y):Float;
	private function get_y():Float {
		return location.y;
	}
	private function set_y(n:Float):Float {
		location.y = n;
		if (!isLocked) update();
		
		return n;
	}
	
	/**
	 * Determines if the events dispatched by the
	 * VirtualMouse instance are IVirualMouseEvent
	 * Events (wrapping Event and MouseEvent) or events
	 * of the native Event and MouseEvent type. When using
	 * non-native events, you can check to see if the
	 * events originated from VirtualMouse by seeing if
	 * the events are of the type IVirualMouseEvent.
	 * @see lastEvent
	 */
	public var useNativeEvents(get_useNativeEvents, set_useNativeEvents):Bool;
	private function get_useNativeEvents():Bool {
		return _useNativeEvents;
	}
	private function set_useNativeEvents(b:Bool):Bool {
		if (b == _useNativeEvents) return false;
		_useNativeEvents = b;
		if (_useNativeEvents){
			eventEvent = VirtualMouseEvent;
			mouseEventEvent = VirtualMouseMouseEvent;
		}else{
			eventEvent = Event;
			mouseEventEvent = MouseEvent;
		}
		
		return b;
	}
	
	/**
	 * Returns the location (x and y) of the current
	 * VirtualMouse instance. The location of the
	 * virtual mouse is based in the global
	 * coordinate space.
	 * @return A Point instance representing the 
	 * 		location of the virtual mouse in
	 * 		global coordinate space.
	 * @see x
	 * @see y
	 * @see setLocation()
	 */
	public function getLocation():Point {
		return location.clone();
	}
	
	/**
	 * Sets the location (x and y) of the current
	 * VirtualMouse instance.  There are two ways to
	 * call setLocation, either passing in a single
	 * Point instance, or by passing in two Number
	 * instances representing x and y coordinates.
	 * The location of the virtual mouse is based in
	 * the global coordinate space.
	 * @param a A Point instance or x Number value.
	 * @param b A y Number value if a is a Number.
	 * @see x
	 * @see y
	 * @see getLocation()
	 */
	public function setLocation(a:Dynamic, b:Dynamic = null):Void 
	{
		//log.debug("VM setLocation", a, b);
		if (Std.is(a, Point)) {
			var loc:Point = untyped a;
			location.x = loc.x;
			location.y = loc.y;
		}else{
			location.x = untyped (a);
			location.y = untyped (b);
		}

		if (!isLocked) update();
	}
	
	/**
	 * Locks the current VirtualMouse instance
	 * preventing updates from being made as 
	 * properties change within the instance.
	 * To release and allow an update, call unlock().
	 * @see lock()
	 * @see update()
	 */
	public function lock():Void {
		isLocked = true;
	}
	
	/**
	 * Unlocks the current VirtualMouse instance
	 * allowing updates to be made for the
	 * dispatching of virtual mouse events. After
	 * unlocking the instance, it will update and
	 * additional calls to press(), release(), or
	 * changing the location of the virtual mouse
	 * will also invoke updates.
	 * @see lock()
	 * @see update()
	 */
	public function unlock():Void {
		isLocked = false;
		update();
	}
	
	/**
	 * Allows you to disable an event by type
	 * preventing the virtual mouse from 
	 * dispatching that event during an update.
	 * @param type The type for the event to
	 * 		disable, e.g. MouseEvent.CLICK
	 * @see enableEvent()
	 */
	public function disableEvent(type:String):Void {
		disabledEvents.set(type, true);
	}
	
	/**
	 * Re-enables an event disabled with
	 * disableEvent.
	 * @param type The type for the event to
	 * 		enable, e.g. MouseEvent.CLICK
	 * @see disableEvent()
	 */
	public function enableEvent(type:String):Void {
		//if (disabledEvents.exists(type)) {
			//disabledEvents.remove(type);
		//}
		disabledEvents.remove(type);
	}
	
	/**
	 * Ignores a display object preventing that
	 * object from recieving events from the
	 * virtual mouse.  This is useful for instances
	 * used for cursors which may always be under
	 * the virtual mouse's location.
	 * @param instance A reference to the
	 * 		DisplayObject instance to ignore.
	 * @see unignore()
	 */
	public function ignore(instance:DisplayObject):Void {
		ignoredInstances.set(instance, true);
	}
	
	/**
	 * Removes an instance from the ignore list
	 * defined by ignore().  When an ingored
	 * object is passed into unignore(), it will
	 * be able to receive events from the virtual
	 * mouse.
	 * @param instance A reference to the
	 * 		DisplayObject instance to unignore.
	 * @see ignore()
	 */
	public function unignore(instance:DisplayObject):Void {
		//if (ignoredInstances.exists(instance)){
			//ignoredInstances.remove(instance);
		//}
		ignoredInstances.remove(instance);
	}
	
	/**
	 * Simulates the pressing of the left
	 * mouse button. To release the mouse
	 * button, use release().
	 * @see release()
	 * @see click()
	 */
	public function press():Void {
		//if (_mouseIsDown) return;
		updateMouseDown = true;
		_mouseIsDown = true;
		if (!isLocked) update();
	}
	
	/**
	 * Simulates the release of the left
	 * mouse button.  This method has no
	 * effect unless press() was called first.
	 * @see press()
	 * @see click()
	 */
	public function release():Void {
		//if (!_mouseIsDown) return;
		updateMouseDown = true;
		_mouseIsDown = false;
		if (!isLocked) update();
	}
	
	/**
	 * Simulates a click of the left
	 * mouse button (press and release)
	 * @see press()
	 * @see release()
	 * @see click()
	 * @see doubleClick()
	 */
	public function click():Void {
		press();
		release();
	}
	
	/**
	 * Simulates a double-click of the left
	 * mouse button (press and release twice).
	 * Calling this command is the only way to
	 * simulate a double-click for the virtual
	 * mouse.  Calling press() and release() or
	 * click() is rapid succession will not
	 * invoke a double-click event. The double-click
	 * event will also only fire for an instance
	 * if it's doubleClickEnabled property is
	 * set to true.
	 * @see click()
	 */
	public function doubleClick():Void {
		// if locked, doubleClick will
		// not fire but the mouse will 
		// be released if not already
		if (isLocked) {
			release();
		}else{

			// call update with a click, press, then release
			// and double-click notification for release
			click();
			press();
			isDoubleClickEvent = true;
			release();
			isDoubleClickEvent = false;
		}
	}
	
	/*Added by Jim Kremens kremens@gmail.com 08/16/07 */
	public function exitContainer():Void {
		if( container == null) return;	
		var targetLocal:Point = target.globalToLocal(location);		
		//log.debug("Targetlocal", target != container);
		if (disabledEvents.get(MouseEvent.MOUSE_OUT) == false) {
			_lastEvent = Type.createInstance( mouseEventEvent, [MouseEvent.MOUSE_OUT, true, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]);
			container.dispatchEvent(Type.createInstance( mouseEventEvent, [MouseEvent.MOUSE_OUT, true, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]));
			dispatchEvent(Type.createInstance( mouseEventEvent, [MouseEvent.MOUSE_OUT, true, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]));
		}
		if (disabledEvents.get(MouseEvent.ROLL_OUT) == false) { // rolls do not propagate
			_lastEvent = Type.createInstance( mouseEventEvent, [MouseEvent.ROLL_OUT, false, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]);
			container.dispatchEvent(Type.createInstance( mouseEventEvent, [MouseEvent.ROLL_OUT, false, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]));
			dispatchEvent(Type.createInstance( mouseEventEvent, [MouseEvent.ROLL_OUT, false, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]));
		}
		if (target != container) {
			if (disabledEvents.get(MouseEvent.MOUSE_OUT) == false) {
				_lastEvent = Type.createInstance( mouseEventEvent, [MouseEvent.MOUSE_OUT, true, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]);
				target.dispatchEvent(Type.createInstance( mouseEventEvent, [MouseEvent.MOUSE_OUT, true, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]));
				dispatchEvent(Type.createInstance( mouseEventEvent, [MouseEvent.MOUSE_OUT, true, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]));
			}
			if (disabledEvents.get(MouseEvent.ROLL_OUT) == false) { // rolls do not propagate
				_lastEvent = Type.createInstance( mouseEventEvent, [MouseEvent.ROLL_OUT, false, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]);
				target.dispatchEvent(Type.createInstance( mouseEventEvent, [MouseEvent.ROLL_OUT, false, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]));
				dispatchEvent(Type.createInstance( mouseEventEvent, [MouseEvent.ROLL_OUT, false, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]));
			}
		}
		//reset the target to the stage, which is the value it starts at.
		target = _stage;
	}
	
	/**
	 * Updates the VirtualMouse instance's state
	 * to reflect a change in the virtual mouse.
	 * Within this method all events will be dispatched.
	 * update() is called any time a VirtualMouse
	 * property is changed unless lock() was used to
	 * lock the instance.  update() will then not be
	 * called until unlock() is used to unlock
	 * the instance. Typically you would never call
	 * update() directly; it is called automatically
	 * by the VirtualMouse class. Calling update()
	 * manually will override lock(). Whenever update()
	 * is called, the UPDATE event is dispatched.
	 * @see lock()
	 * @see unlock()
	 */
	public function update():Void {
		// dispatch an update event indicating that 
		// an update has occured
		dispatchEvent(new Event(UPDATE, false, false));
	}
	
	private function handleUpdate(event:Event):Void 
	{
		if (container == null) return;
			
		// go through each objectsUnderPoint checking:
		// 		1) is not ignored
		//		2) is InteractiveObject
		//		3) mouseEnabled
		// 		4) all parents have mouseChildren
		// if not interactive object, defer interaction to next object in list
		// if is interactive and enabled, give interaction and ignore rest
		
		// var p:Point = CoordinateTools.localToLocal(container, stage, location);
		//var objectsUnderPoint:Array = container.getObjectsUnderPoint(p); 
		
		if( container.scrollRect != null){
			PaperLogger.warning("The container that virtualMouse is trying to test against has a scrollRect defined, and may cause an issue with finding objects under a defined point.  Use MovieMaterial.rect to set a rectangle area instead");
		}
		var originalPoint:Point = new Point();
		originalPoint.x = container.x;
		originalPoint.y = container.y;
		container.x = container.y = 0;
		
		var objectsUnderPoint:Array<DisplayObject> = container.getObjectsUnderPoint(location);
		
		container.x = originalPoint.x;
		container.y = originalPoint.y;
		
		var currentTarget:InteractiveObject = null;
		var currentParent:DisplayObject = null;
		
		var i:Int = objectsUnderPoint.length;
		
		while (i-- != 0) {
			currentParent = objectsUnderPoint[i];
			
			// go through parent hierarchy
			while (currentParent != null) {
				
				// don't use ignored instances as the target
				if (ignoredInstances.get(currentParent)) {		
					currentTarget = null;
					break;
				}
				
				// invalid target if in a SimpleButton
				if (currentTarget != null && Std.is(currentParent, SimpleButton)) 
				{
					//log.debug("found SimpleButton - setting currentTarget to null");
					currentTarget = null;
					
				// invalid target if a parent has a
				// false mouseChildren
				} else if (currentTarget != null && (untyped (currentParent).mouseChildren) == null) 
				{
					//log.debug("parent false mouseChildren - setting currentTarget to null");
					currentTarget = null;
				}
				
				// define target if an InteractiveObject
				// and mouseEnabled is true
				if (currentTarget == null && Std.is(currentParent, InteractiveObject) && untyped (currentParent).mouseEnabled) 
				{
					//log.debug("found InteractiveObject && mouseEnabled = true - setting currentTarget");
					currentTarget = untyped (currentParent);
				}
				
				// next parent in hierarchy
				currentParent = currentParent.parent;
			}
			
			// if a currentTarget was found
			// ignore all other objectsUnderPoint
			if (currentTarget != null){
				break;
			}
		}
		
		
		
		// if a currentTarget was not found
		// the currentTarget is the stage
		if (currentTarget == null)
		{
			//trace("no CurrentTarget", container.hitTestPoint(location.x, location.y));
			currentTarget = container;
			//currentTarget = _stage;
			//log.debug("no new target found, using stage");
		}
		
		// get local coordinate locations
		var targetLocal:Point = target.globalToLocal(location);
		var currentTargetLocal:Point = currentTarget.globalToLocal(location);
		
		
		// move event
		if (lastLocation.x != location.x || lastLocation.y != location.y) 
		{				
			var withinStage:Bool = false;
			if(stage != null) withinStage = (location.x >= 0 && location.y >= 0 && location.x <= stage.stageWidth && location.y <= stage.stageHeight);
			
			// mouse leave if left stage
			if (!withinStage && lastWithinStage && !disabledEvents.get(Event.MOUSE_LEAVE)){
				_lastEvent = Type.createInstance( eventEvent, [Event.MOUSE_LEAVE, false, false]);
				stage.dispatchEvent(_lastEvent);
				dispatchEvent(_lastEvent);
			}
			
			// only mouse move if within stage
			if (withinStage && !disabledEvents.get(MouseEvent.MOUSE_MOVE)){
				_lastEvent = Type.createInstance( mouseEventEvent, [MouseEvent.MOUSE_MOVE, true, false, currentTargetLocal.x, currentTargetLocal.y, currentTarget, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]);
				currentTarget.dispatchEvent(_lastEvent);
				dispatchEvent(_lastEvent);
			}
			
			// remember if within stage
			lastWithinStage = withinStage;
		}
		
		// roll/mouse (out and over) events 
		
		if (currentTarget != target) 
		{	
			// off of last target
			if (!disabledEvents.get(MouseEvent.MOUSE_OUT)){
				_lastEvent = Type.createInstance( mouseEventEvent, [MouseEvent.MOUSE_OUT, true, false, targetLocal.x, targetLocal.y, currentTarget, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]);
				target.dispatchEvent(_lastEvent);
				dispatchEvent(_lastEvent);
			}
			if (!disabledEvents.get(MouseEvent.ROLL_OUT)){ // rolls do not propagate
				_lastEvent = Type.createInstance( mouseEventEvent, [MouseEvent.ROLL_OUT, false, false, targetLocal.x, targetLocal.y, currentTarget, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]);
				target.dispatchEvent(_lastEvent);
				dispatchEvent(_lastEvent);
			}
			
			// on to current target
			if (!disabledEvents.get(MouseEvent.MOUSE_OVER)){
				//log.debug("*** MOUSEOVER****");
				_lastEvent = Type.createInstance( mouseEventEvent, [MouseEvent.MOUSE_OVER, true, false, currentTargetLocal.x, currentTargetLocal.y, target, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]);
				currentTarget.dispatchEvent(_lastEvent);
				dispatchEvent(_lastEvent);
			}
			if (!disabledEvents.get(MouseEvent.ROLL_OVER)){ // rolls do not propagate
				//log.debug("*** ROLLOVER****");
				_lastEvent = Type.createInstance( mouseEventEvent, [MouseEvent.ROLL_OVER, false, false, currentTargetLocal.x, currentTargetLocal.y, target, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]);
				currentTarget.dispatchEvent(_lastEvent);
				dispatchEvent(_lastEvent);
			}
		
		
		}
		
		// click/up/down events
		if (updateMouseDown) {
			if (_mouseIsDown) {
				
				if (!disabledEvents.get(MouseEvent.MOUSE_DOWN)){
					_lastEvent = Type.createInstance( mouseEventEvent, [MouseEvent.MOUSE_DOWN, true, false, currentTargetLocal.x, currentTargetLocal.y, currentTarget, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]);
					currentTarget.dispatchEvent(_lastEvent);
					dispatchEvent(_lastEvent);
				}
				
				// remember last down
				lastDownTarget = currentTarget;
				updateMouseDown = false;
			// mouse is up
			}else{
				if (!disabledEvents.get(MouseEvent.MOUSE_UP)){
					_lastEvent = Type.createInstance( mouseEventEvent, [MouseEvent.MOUSE_UP, true, false, currentTargetLocal.x, currentTargetLocal.y, currentTarget, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]);
					currentTarget.dispatchEvent(_lastEvent);
					dispatchEvent(_lastEvent);
				}
				
				if (!disabledEvents.get(MouseEvent.CLICK) && currentTarget == lastDownTarget) {
					_lastEvent = Type.createInstance( mouseEventEvent, [MouseEvent.CLICK, true, false, currentTargetLocal.x, currentTargetLocal.y, currentTarget, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]);
					currentTarget.dispatchEvent(_lastEvent);
					dispatchEvent(_lastEvent);
				}
				
				// clear last down
				lastDownTarget = null;
				updateMouseDown = false;
			}
		}
		
		// explicit call to doubleClick()
		if (isDoubleClickEvent && !disabledEvents.get(MouseEvent.DOUBLE_CLICK) && currentTarget.doubleClickEnabled) {
			_lastEvent = Type.createInstance( mouseEventEvent, [MouseEvent.DOUBLE_CLICK, true, false, currentTargetLocal.x, currentTargetLocal.y, currentTarget, ctrlKey, altKey, shiftKey, _mouseIsDown, delta]);
			currentTarget.dispatchEvent(_lastEvent);
			dispatchEvent(_lastEvent);
		}
		
		// remember last values
		lastLocation = location.clone();
		lastMouseDown = _mouseIsDown;	
		target = currentTarget;
	}
	
	private function keyHandler(event:KeyboardEvent):Void {
		// update properties used in MouseEvents
		altKey = event.altKey;
		ctrlKey = event.ctrlKey;
		shiftKey = event.shiftKey;
	}
}
